문서의 임의 삭제는 제재 대상으로, 문서를 삭제하려면 삭제 토론을 진행해야 합니다. 문서 보기문서 삭제토론 디자인 패턴 (문단 편집) ===== 전략 패턴을 활용한 코딩 ===== 이 문제에서 근본적인 문제는 '좌클릭, 우클릭, WASD를 사용하는데 드는 무기에 따라 작동 방식이 다 달라진다' 라는 점에 있다. 그러나 뒤집어 말한다면, '좌클릭, 우클릭, WASD라는 기본적인 틀은 있다' 라는 의미가 된다. 어떤 무기를 들든 해야 할 명령 자체는 정해져있지만 그로 인해 발생하는 행위가 달라진다는 것이다. 따라서 이 때 '전략'이라는 객체가 행해야 할 수단은 이하와 같다. 1. 내가 내릴 수 있는 공통적인 명령의 '''껍데기'''만 인터페이스로 지정한다. 이를 '''캡슐화'''라고 한다. 2. 각각의 명령에 대해서 상황에 따라 취해야 하는 독립적 행동을 지정한다. 이게 '''스트레티지'''가 된다. 3. 그 독립적 행동을 현재 상황에 따라 껍데기 안에서 갈아끼우게만 한다. 이를 '''클라이언트'''라고 한다. 우선 '캡슐화'에 대해서 알아보자. 앞서 이야기했듯 무슨 무기를 들든 해야 할 명령은 정해져있다. 이 모든 것들을 인터페이스로 묶어 하위 개체에서 수정할 수 있도록 함수의 껍데기만 지정한다. {{{#!syntax csharp public interface LeftClick{ public void left(); } public interface RightClick{ public void right(); } public interface WASD{ public void wasd(); } }}} 이렇게 껍데기를 지정했다면 각각의 명령이 취할 수 있는 모든 '전략'을 생각해본다. 예를 들어 left키를 누르면 '칼로 벤다, 권총을 쏜다, 저격총을 쏜다'의 3가지 패턴이, right키를 누르면 '줌 인을 한다', '아무 일도 없다'의 2가지, wasd키를 누르면 '움직인다', '움직이지 않는다'의 2가지 패턴이 있을 것이다. 그 모든 패턴 하나하나를 해당 인터페이스의 하위 인터페이스로 작성하고, 각각의 기능을 추가해준다. {{{#!syntax csharp public class KnifeLeft : LeftClick{ public override void left(){ /* 칼로 벤다. */ }; } public class PistolLeft : LeftClick{ public override void left(){ /* 권총을 쏜다. */ }; } public class SnipeLeft : LeftClick{ public override void left(){ /* 저격총을 쏜다. */ }; } public class ZoomRight : RightClick{ public override void right(){ /* 줌 인을 한다. */ }; } public class NothingRight : RightClick{ public override void right(){ /* 아무 일도 없다. */ }; } public class WASDMovable : WASD{ public override void wasd(){ /* 방향키대로 움직인다. */ }; } public class WASDUnmovable : WASD{ public override void wasd(){ /* 움직이지 못한다. */ }; } }}} 그 다음에 무기를 들었을 때 할 수 있는 모든 행동을 클래스화한다. 그리고 할 수 있는 모든 전략을 내부 변수로 구성하고, 그 행동을 취할 경우 각 전략에서 지정한 함수를 실행하도록 코딩한다. {{{#!syntax csharp public class Weapon{ public LeftClick leftStrategy; public RightClick rightStrategy; public WASD wasdStrategy; public void left(){ leftStrategy.left(); } public void right(){ rightStrategy.right(); } public void wasd(){ wasdStrategy.wasd(); } } }}} 이제 만들어진 이 Weapon이 바로 '전략 패턴' 객체가 된다. 앞에서 말한 칼, 권총, 저격총을 이 Weapon을 상속받게 만들고 각각에 해당하는 전략을 할당해준다. {{{#!syntax csharp public class Knife : Weapon{ public Knife(){ leftStrategy = new KnifeLeft(); rightStrategy = new NothingRight(); wasdStrategy = new WASDMovable(); } } public class Pistol : Weapon{ public Pistol(){ leftStrategy = new PistolLeft(); rightStrategy = new NothingRight(); wasdStrategy = new WASDMovable(); } } public class Snipe : Weapon{ public Snipe(){ leftStrategy = new SnipeLeft(); rightStrategy = new ZoomRight(); wasdStrategy = new WASDUnmovable(); } } }}} 마지막으로 이 Weapon들을 내부에서 바꿔가면서 실제 left(), right(), wasd() 함수를 실행하는 객체, 즉 플레이어를 클라이언트로 만든다. {{{#!syntax csharp public class Player{ public readonly Knife knife; public readonly Pistol pistol; public readonly Snipe snipe; public Weapon nowWeapon; public Player(){ knife = new Knife(); pistol = new Pistol(); snipe = new Snipe(); nowWeapon = knife; } public void left(){ nowWeapon.left(); } public void right(){ nowWeapon.right(); } public void wasd(){ nowWeapon.wasd(); } } }}} 이제 nowWeapon을 knife, pistol, snipe로 갈아끼운다고 해도 클라이언트에는 아무 변화도 없이 그냥 nowWeapon에 할당된 함수를 실행한다. 거슬러 올라가면 이 nowWeapon마다 left, right, wasd에 해당하는 전략을 찾아가고, 그 전략 클래스로 거슬러 올라가 지정한 행동을 하게 된다. 예를 들어 이제 nowWeapon에 knife가 들어가있다고 하면 left = leftStrategy.left()를 실행하는데, 그 leftStrategy가 KnifeLeft로 지정되어 있으므로 left() = { /* 칼로 벤다. */ }; 가 실행되는 것이다. 코드를 얼핏 보면 "해결한다고 해놓고 무슨 클래스만 한가득 생기고 몇 단계씩 계속 빙빙 돌게 꼬아놔서 더 복잡해진 거 아니냐?" 라는 소리가 나올 수 있다. 실제로 이 코드 자체는 코드만으로 보면 훨씬 더 복잡해진 것이 맞으나, 유지보수 측면에서 매우 큰 장점을 갖는다. '행동', '전략', '수행'을 각각 다른 클래스로 분할해버렸기 때문에 전략을 바꾸고 싶으면 전략만 수정하면 되고, 행동을 바꾸고 싶다면 행동만 바꾸면 된다. 그렇게 되는 한편으로도 '수행'은 새 무기나 새 행동이 생길 때만 조금씩 수정해주면 된다. 예를 들어서 이 코드에서 '권총이 너무 센 것 같아, 권총을 들면 플레이어가 못 움직이게 만들자' 라고 한다면, {{{#!syntax csharp public class Pistol : Weapon{ public Pistol(){ leftStrategy = new PistolLeft(); rightStrategy = new NothingRight(); //wasdStrategy = new WASDMovable(); wasdStrategy = new WASDUnmovable(); } } }}} 이런 방식으로 상속을 따로 건드릴 필요 없이 그냥 Pistol에 할당된 wasdStrategy만 바꿔버리면 해결된다. 그러다가 '이러니까 권총이 너무 약하네, 그럼 타협해서 권총을 들면 움직일 순 있어도 느리게 움직이게 만들자' 라는 패치를 할 경우에도, {{{#!syntax csharp public class WASDSlowmovable : WASD{ public override void wasd(){ /* 방향키대로 많이 느리게 움직인다. */ }; } public class Pistol : Weapon{ public Pistol(){ leftStrategy = new PistolLeft(); rightStrategy = new NothingRight(); //wasdStrategy = new WASDUnmovable(); wasdStrategy = new WASDSlowmovable(); } } }}} 느리게 움직이는 WASD 전략을 하나 더 만들어주고 Pistol의 wasdStrategy를 바꿔주면 된다. 반대로 이런 개개인의 처리가 아니라 '플레이어가 너무 빠르니 전반적인 이동 속도를 느리게 하자' 같은 경우, {{{#!syntax csharp public class WASDMovable : WASD{ //public override void wasd(){ /* 방향키대로 움직인다. */ }; public override void wasd(){ /* 방향키대로 움직이는데, 좀 느린 속도로. */ }; } }}} 일괄적으로 행해지는 행동 부분을 수정하면 WASDMovable 전략을 쓰는 모든 무기가 영향을 받으므로 일괄 수정도 간단하다. 물론 앞에서 말한 Slowmovable 같은 건 또 따로 수정해줘야 하겠지만, 그걸 사용하지 않는 추후 수정되는 무기들은 일괄적으로 느려진 이동속도 전략을 상속받게 될 것이다. 게다가 이런 식으로 만들어놓고 나중에 '새로운 무기인 방패. 좌클릭은 기능 없음, 우클릭을 들면 방패를 듦. 장비하면 느린 속도로 이동함' 같은 패치를 할 때도, {{{#!syntax csharp public class NothingLeft : LeftClick{ public override void left(){ /* 아무 일도 일어나지 않는다. */ }; // 좌클릭이 기능하지 않는 새 전략 } public class DefenceRight : RightClick{ public override void right(){ /* 방패를 들어 전방의 공격을 막는다. */ }; // 우클릭으로 방어하는 새 전략 } public class Shield : Weapon{ public Shield(){ // 새로운 무기인 방패의 class leftStrategy = new NothingLeft(); rightStrategy = new DefenceRight(); // 방패에 맞는 새 좌클릭, 우클릭 전략 배정 wasdStrategy = new WASDSlowmovable(); // 이미 쓰던 전략을 재탕 } } public class Player{ public readonly Knife knife; public readonly Pistol pistol; public readonly Snipe snipe; public readonly Shield shield; // 클라이언트에 방패 추가해주고 public Weapon nowWeapon; public Player(){ knife = new Knife(); pistol = new Pistol(); snipe = new Snipe(); shield = new Shield(); // 선언만 해주면 끝. nowWeapon = knife; } public void left(){ nowWeapon.left(); } public void right(){ nowWeapon.right(); } public void wasd(){ nowWeapon.wasd(); } } }}} 물론 전략 패턴이 만능인 것은 아니다. 이 경우에도 만약 좌클릭, 우클릭, WASD 외에 R키, Q키 같은 새로운 키가 생겨나기 시작하면 그 키마다 새로운 전략을 생성하고 기존에 있는 무기에도 새로운 키에 맞는 전략을 배정해줘야 하며, 클라이언트에서도 해당 전략을 통한 함수를 추가해줘야 하기 때문이다. 이는 앞서 말한 첫 번째 예시에서 보이듯이 더미 함수를 계속 추가시키는 것과 얼핏 보면 다를 게 없어보인다. 그러나 이 경우에도 전략 패턴은 첫 번째 예시보다 더욱 간편하다. 첫 번째 예시에서는 더미 함수를 '''해당 키를 사용하지 않는 모든 무기'''에 대해, '''아무 일도 일어나지 않음'''이라는 똑같은 함수를 일일이 배정해줘야 하기 때문이다. 배정하다가 실수가 생길 수 있고, 나중에 '고심 끝에 모든 무기에 줌을 넣기로 했음' 같은 패치가 생겼다간 지정해주었던 더미 함수를 모조리 수정해야 하는 참사가 생길 수도 있다. 그러나 전략 패턴을 사용할 경우, '''해당 키를 사용하지 않는 모든 무기'''에 일일이 배정해줘야 하는 것은 같아도, '''아무 일도 일어나지 않는다는 전략 하나'''만 만들고 그걸로 죄 다 할당시켜주면 되기 때문에 간편하다. 예를 들어 나중에 R키를 사용하는 무기가 생긴다면, {{{#!syntax csharp public interface RClick{ public void keyboardr(); // R키를 눌러서 발생하는 인터페이스 캡슐화 } public class NothingR : RClick{ public override keyboardr(){ /* 아무 일도 일어나지 않음. */ } // 미작동한다는 전략 생성 } public class Weapon{ public LeftClick leftStrategy; public RightClick rightStrategy; public WASD wasdStrategy; public RClick rStrategy; // R키를 눌렀을 때의 전략 생성 public void left(){ leftStrategy.left(); } public void right(){ rightStrategy.right(); } public void wasd(){ wasdStrategy.wasd(); } public void keyboardr(){ rStrategy.keyboardr(); } // 전략을 따라가도록 함수 배정 } public class Knife : Weapon{ public Knife(){ leftStrategy = new KnifeLeft(); rightStrategy = new NothingRight(); wasdStrategy = new WASDMovable(); rStrategy = new NothingR(); // 해당 기능을 안 쓰는 무기에 대해서는 아무 것도 안 하는 전략 배정 } } public class Player{ public readonly Knife knife; public readonly Pistol pistol; public readonly Snipe snipe; public readonly Shield shield; public Weapon nowWeapon; public Player(){ knife = new Knife(); pistol = new Pistol(); snipe = new Snipe(); shield = new Shield(); nowWeapon = knife; } public void left(){ nowWeapon.left(); } public void right(){ nowWeapon.right(); } public void wasd(){ nowWeapon.wasd(); } public void keyboardr(){ nowWeapon.keyboardr(); } // 클라이언트에서 R키에 반응하는 함수 지정. } }}} 하는 식으로 비교적 간단하게 새로운 키를 배정시킬 수 있다.저장 버튼을 클릭하면 당신이 기여한 내용을 CC-BY-NC-SA 2.0 KR으로 배포하고,기여한 문서에 대한 하이퍼링크나 URL을 이용하여 저작자 표시를 하는 것으로 충분하다는 데 동의하는 것입니다.이 동의는 철회할 수 없습니다.캡챠저장미리보기